package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.product.model.*;
import com.atguigu.gmall.product.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * title:
 * author: Gyh
 * date:2023-04-19 18:23
 * Description:
 */
@Service
public class SkuManageServiceImpl implements SkuManageService {

    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private SkuImageService skuImageService;

    @Autowired
    private SkuAttrValueService skuAttrValueService;

    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitService rabbitService;


    /**
     * 保存SkuInfo,SKU信息
     * @param skuInfo
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveSkuInfo(SkuInfo skuInfo) {
        //将提交的商品SKU基本信息放到SkuInfo对象,存入sku_info表中
        skuInfoService.save(skuInfo);
        Long skuInfoId = skuInfo.getId();
        //将提交商品SKU图片放入SkuImage集合中,存入sku_image
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        //判断集合不为空
        if (!CollectionUtils.isEmpty(skuImageList)){
            skuImageList.stream().forEach(skuImage -> {
                skuImage.setSkuId(skuInfoId);
            });
            skuImageService.saveBatch(skuImageList);
        }
        //将提交商品sku属性信息放到SkuAttrValue集合中,存入sku_attr_value表中
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)){
            skuAttrValueList.stream().forEach(skuAttrValue -> {
                skuAttrValue.setSkuId(skuInfoId);
            });
            skuAttrValueService.saveBatch(skuAttrValueList);
        }
        //将提交商品sku属性信息放到SkuSaleAttrValue集合中,存入sku_sale_attr_value表中
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if (!CollectionUtils.isEmpty(skuSaleAttrValueList)){
            skuSaleAttrValueList.stream().forEach(skuSaleAttrValue -> {
                skuSaleAttrValue.setSkuId(skuInfoId);
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
            });
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);
        }
        //将新增的商品skuid存入布隆过滤器
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        bloomFilter.add(skuInfo.getId());
    }

    /**
     * sku分页列表
     * @param skuInfoPage
     * @param category3Id
     * @return
     */
    @Override
    public Page<SkuInfo> getSkuListByPage(Page<SkuInfo> skuInfoPage, Long category3Id) {
        //创建查询条件
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SkuInfo::getCategory3Id,category3Id);
        queryWrapper.orderByDesc(SkuInfo::getUpdateTime);
        //执行分页查询
        Page<SkuInfo> page = skuInfoService.page(skuInfoPage, queryWrapper);
        return page;
    }

    /**
     * 商品SKU上架
     * @param skuId
     * @return
     */
    @Override
    public void onSale(Long skuId) {
        //创建SkuInfo对象
        SkuInfo skuInfo = new SkuInfo();
        //传入库存id
        skuInfo.setId(skuId);
        //修改上架,销售字段is_sale （1：是 0：否）
        skuInfo.setIsSale(1);
        //修改
        skuInfoService.updateById(skuInfo);
        //商品上架后 需要同步更新ES和Redis缓存
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_UPPER, skuId);
    }

    /**
     * 商品SKU下架
     * @return
     */
    @Override
    public void cancelSale(Long skuId) {
        LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SkuInfo::getId, skuId);
        updateWrapper.set(SkuInfo::getIsSale, 0);
        skuInfoService.update(updateWrapper);
        //商品下架后 需要同步更新ES和Redis缓存
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_LOWER,skuId);
    }

    /**
     * 利用缓存获取商品的信息
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        //采用redis分布式锁
//        SkuInfo skuInfo = getSkuInfoBySpringDataRedis(skuId);
        //采用Redisson分布式锁
        SkuInfo skuInfo = getSkuInfoByRedisson(skuId);
        return skuInfo;
    }

    /**
     * 利用缓存获取商品的信息，采用Redisson实现分布式锁
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfoByRedisson(Long skuId) {
        try {
            //优先从分布式缓存中获取商品的信息
            //构建缓存业务数据key sku:1:info
            String dataKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            //从缓存中获取数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            //如果命中缓存，直接响应数据接口
            if (skuInfo!=null){
                return skuInfo;
            }
            //尝试获取分布式锁
            //构建商品分布式锁，格式sku:1:lock
            String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            //创建分布式锁的值，采用UUID，避免锁相互释放
            String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
            //尝试获取锁
            RLock lock = redissonClient.getLock(lockKey);
            Boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX2,RedisConst.SKULOCK_EXPIRE_PX1,TimeUnit.SECONDS);
            if (flag){
                try {
                    //执行业务
                    this.getSkuInfoFromDB(skuId);
                    //查询到的空数据也放入缓存中，缓存时间10min
                    if (skuInfo==null){
                        redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                        return skuInfo;
                    }else {
                        //查询到存在的数据,放入缓存中，存储24h
                        redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                        return skuInfo;
                    }
                }finally {
                    //释放锁
                    lock.unlock();
                }
            }else {
                //获取锁失败，进行自旋
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfoByRedisson(skuId);
            }
        } catch (Exception e) {
            //Redis服务异常，在去查询数据库
            return this.getSkuInfoFromDB(skuId);
        }
    }

    /**
     * 利用缓存获取商品的信息，采用分布式锁避免缓存击穿
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfoBySpringDataRedis(Long skuId) {
        try {
            //优先从分布式缓存中获取商品的信息
            //构建缓存业务数据key sku:1:info
            String dataKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            //从缓存中获取数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            //如果命中缓存，直接响应数据接口
            if (skuInfo!=null){
                return skuInfo;
            }
            //尝试获取分布式锁
            //构建商品分布式锁，格式sku:1:lock
            String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            //创建分布式锁的值，采用UUID，避免锁相互释放
            String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
            //通过set ex nx 命令实现分布式锁
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey,lockVal,RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            //如果获取锁成功
            if(flag){
                try {
                    //执行业务
                    this.getSkuInfoFromDB(skuId);
                    //查询到的空数据也放入缓存中，缓存时间10min
                    if (skuInfo==null){
                        redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                        return skuInfo;
                    }else {
                        //查询到存在的数据,放入缓存中，存储24h
                        redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                        return skuInfo;
                    }
                }  catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁，使用Lua脚本以保证判断和删除锁的原子性
                    //创建脚本
                    String luaText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
                    script.setScriptText(luaText);
                    script.setResultType(Long.class);
                    //执行脚本
                    redisTemplate.execute(script,Arrays.asList(lockKey),new String[]{lockVal});
                }
            }else {
                //获取锁失败，进行自旋，尝试再次获取锁
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfoBySpringDataRedis(skuId);
            }
        } catch (Exception e) {
            //Redis服务异常，在去查询数据库
            return this.getSkuInfoFromDB(skuId);
        }
    }


    /**
     * 从数据库中获取商品信息
     * @param skuId
     * @return
     */
    @Override
    @GmallCache(prefix = "sku:")
    public SkuInfo getSkuInfoFromDB(Long skuId) {
        //根据商品SkuID（主键）查询sku_info表得到商品信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        //根据skuId查询商品图片表sku_image得到商品图片列表
        if (skuInfo!=null){
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId,skuId);
            List<SkuImage> skuImageList = skuImageService.list(queryWrapper);
            //封装图片列表
            skuInfo.setSkuImageList(skuImageList);
            return skuInfo;
        }
        return null;
    }

    /**
     * 根据skuId 获取最新的商品价格
     * @param skuId
     * @return
     */
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo!=null){
            //取出价格
            BigDecimal price = skuInfo.getPrice();
            return price;
        }
        return new BigDecimal("0.00");
    }

}
